package hikari.gui.panels.bullet;

import com.gooddies.events.ValueChangedEvent;
import com.gooddies.swing.RowLayout;
import com.gooddies.swing.hCheckBox;
import com.gooddies.swing.hComboBox;
import com.gooddies.texteditors.DefaultFloatTextField;
import com.gooddies.wiring.annotations.PostWiring;
import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import hikari.gui.guicomponents.hLabel;
import hikari.gui.panels.particle.Vector3dEditor;
import hikari.hcomponents.HComponent;
import hikari.hcomponents.RenderEvent;
import hikari.hcomponents.physics.*;
import hikari.managers.Manager3D;
import hikari.objects3d.Basic3dObject;
import hikari.utils.Utils;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.swig.jirr.ISceneNode;
import org.swig.jirr.vector3df;

/**
 * @author sad
 */
@WiringComponent(singleton = true, lazy = true)
public class BulletPhysicsPanel extends HComponent {

    @Wire
    private Manager3D manager3D;
    private PhysicsData data;
    private Basic3dObject object;
    private DefaultFloatTextField massEB;
    private DefaultFloatTextField restitutionEB;
    private DefaultFloatTextField frictionEB;
    private DefaultFloatTextField rollingFrictionEB;
    private DefaultFloatTextField ccdMotionThresholdEB;
    private DefaultFloatTextField contactProcessingThresholdEB;
    private DefaultFloatTextField ccdSweptRadiusEB;
    private DefaultFloatTextField linearDampingEB;
    private DefaultFloatTextField angularDampingEB;
    private DefaultFloatTextField deactivationTimeEB;
    private DefaultFloatTextField hitFractionEB;
    private Vector3dEditor linearVelocityEB;
    private Vector3dEditor linearFactorEB;
    private Vector3dEditor angularVelocityEB;
    private Vector3dEditor angularFactorEB;
    private Vector3dEditor anisotropicFrictionEB;
    private Vector3dEditor offsetPositionEB;
    private Vector3dEditor offsetRotationEB;
    private hCheckBox disableDeactivationCB;
    private hCheckBox kinematicCB;
    private hCheckBox characterCB;
    private hCheckBox staticCB;
    private hComboBox<EBulletRigidBodyType> rigidBodyTypeCB;
    private IBulletRigidBodyEditPanel editRigidBodyPanel;
    @Wire
    private Utils utils;

    private BulletPhysicsPanel() {
    }

    @PostWiring
    private void init() {
        setTitle("Rigid Body");
        setLayout(new RowLayout().setStartXGap(10).setStartYGap(20).setEndYGap(10).setBetweenYGap(10));
        massEB = createTextField();
        massEB.setMinMax(0, 999);
        add(new hLabel("Mass"));
        add(massEB, RowLayout.NewRow);

        restitutionEB = createTextField();
        restitutionEB.setMinMax(-999, 999);
        add(new hLabel("Restitution"));
        add(restitutionEB, RowLayout.NewRow);

        frictionEB = createTextField();
        frictionEB.setMinMax(-999, 999);
        add(new hLabel("Friction"));
        add(frictionEB, RowLayout.NewRow);

        rollingFrictionEB = createTextField();
        rollingFrictionEB.setMinMax(-999, 999);
        add(new hLabel("Roll friction"));
        add(rollingFrictionEB, RowLayout.NewRow);

        ccdMotionThresholdEB = createTextField();
        ccdMotionThresholdEB.setMinMax(-999, 999);
        add(new hLabel("Motion threshold"));
        add(ccdMotionThresholdEB, RowLayout.NewRow);

        contactProcessingThresholdEB = createTextField();
        contactProcessingThresholdEB.setMinMax(-999, 999);
        add(new hLabel("Contact threshold"));
        add(contactProcessingThresholdEB, RowLayout.NewRow);

        ccdSweptRadiusEB = createTextField();
        ccdSweptRadiusEB.setMinMax(0, 999);
        add(new hLabel("Swept Radius"));
        add(ccdSweptRadiusEB, RowLayout.NewRow);

        linearDampingEB = createTextField();
        linearDampingEB.setMinMax(0, 999);
        add(new hLabel("Linear Damping"));
        add(linearDampingEB, RowLayout.NewRow);

        angularDampingEB = createTextField();
        angularDampingEB.setMinMax(0, 999);
        add(new hLabel("Angular Damping"));
        add(angularDampingEB, RowLayout.NewRow);

        deactivationTimeEB = createTextField();
        deactivationTimeEB.setMinMax(0, 999);
        add(new hLabel("Deactivation Time"));
        add(deactivationTimeEB, RowLayout.NewRow);

        hitFractionEB = createTextField();
        hitFractionEB.setMinMax(0, 999);
        add(new hLabel("Hit Fraction"));
        add(hitFractionEB, RowLayout.NewRow);

        add(new hLabel("Linear Velocity"), RowLayout.NewRow);
        linearVelocityEB = new Vector3dEditor(this);
        onChangeVector(linearVelocityEB);

        add(new hLabel("Linear Factor"), RowLayout.NewRow);
        linearFactorEB = new Vector3dEditor(this);
        onChangeVector(linearFactorEB);

        add(new hLabel("Angular Velocity"), RowLayout.NewRow);
        angularVelocityEB = new Vector3dEditor(this);
        onChangeVector(angularVelocityEB);

        add(new hLabel("Angular Factor"), RowLayout.NewRow);
        angularFactorEB = new Vector3dEditor(this);
        onChangeVector(angularFactorEB);

        add(new hLabel("Anisotropic friction"), RowLayout.NewRow);
        anisotropicFrictionEB = new Vector3dEditor(this);
        onChangeVector(anisotropicFrictionEB);

        add(new hLabel("Offset position"), RowLayout.NewRow);
        offsetPositionEB = new Vector3dEditor(this);
        onChangeVector(offsetPositionEB);

        add(new hLabel("Offset rotation"), RowLayout.NewRow);
        offsetRotationEB = new Vector3dEditor(this);
        onChangeVector(offsetRotationEB);

        disableDeactivationCB = new hCheckBox("Disable deactivation");
        onChangeBoolean(disableDeactivationCB);
        add(disableDeactivationCB, RowLayout.NewRow);

        kinematicCB = new hCheckBox("Is kinematic");
        onChangeBoolean(kinematicCB);
        add(kinematicCB, RowLayout.NewRow);

        characterCB = new hCheckBox("Is character");
        onChangeBoolean(characterCB);
        add(characterCB, RowLayout.NewRow);

        staticCB = new hCheckBox("Is static");
        onChangeBoolean(staticCB);
        add(staticCB, RowLayout.NewRow);

        rigidBodyTypeCB = new hComboBox<>();
        rigidBodyTypeCB.setPreferredSize(new Dimension(150, 20));
        add(new hLabel("Rigidbody type:"), RowLayout.NewRow);
        add(rigidBodyTypeCB, RowLayout.NewRow);
        rigidBodyTypeCB.setValueChangedEvent(new ValueChangedEvent<EBulletRigidBodyType>() {
            @Override
            public void valueChanged(EBulletRigidBodyType value) {
                changeRigidBodyType(value);
                setPreferredSize(null);
                validate();
                repaint();
            }
        });

        fillRigidBodyTypeCombo();
    }

    @SuppressWarnings("unchecked")
    private void fillRigidBodyTypeCombo() {
        for (EBulletRigidBodyType type : EBulletRigidBodyType.values()) {
            rigidBodyTypeCB.addItem(type);
        }

        rigidBodyTypeCB.setSelectedIndex(0);
    }

    private void changeRigidBodyType(EBulletRigidBodyType rigidbodyType) {
        removeRigidBodyPanel();
        IRigidBody rigidBody = createRigidBody(rigidbodyType, object.getMeshSceneNode());

        setRigidBodyType(rigidBody);
    }

    private void setRigidBodyType(IRigidBody rigidBody) {
        data.setRigidBody(rigidBody);
        IBulletRigidBodyEditPanel editPanel = rigidBody.getEditPanel();
        editPanel.onShow(object.getMeshSceneNode(), manager3D);
        setBulletRigidBodyEditPanel(editPanel);
    }

    private IRigidBody createRigidBody(EBulletRigidBodyType rigidbodyType, ISceneNode node) {
        switch (rigidbodyType) {
            case Box:
                return new BoxRigidBody(object.getMeshSceneNode(), data);
            case Sphere:
                return new SphereRigidBody(object.getMeshSceneNode(), data);
            case Cylinder:
                return new CylinderRigidBody(object.getMeshSceneNode(), data);
            case ConvexHull:
                return new ConvexHullRigidBody(object.getMeshSceneNode(), data);
            case Trimesh:
                return new TrimeshRigidBody(object.getMeshSceneNode(), data);
        }

        throw new RuntimeException("createRigidBody is not define handler for " + rigidbodyType.toString());
    }

    private EBulletRigidBodyType getBulletRigidBodyType(IRigidBody rigidBody) {
        if (rigidBody instanceof BoxRigidBody) {
            return EBulletRigidBodyType.Box;
        }
        if (rigidBody instanceof SphereRigidBody) {
            return EBulletRigidBodyType.Sphere;
        }
        if (rigidBody instanceof CylinderRigidBody) {
            return EBulletRigidBodyType.Cylinder;
        }
        if (rigidBody instanceof ConvexHullRigidBody) {
            return EBulletRigidBodyType.ConvexHull;
        }
        if (rigidBody instanceof TrimeshRigidBody) {
            return EBulletRigidBodyType.Trimesh;
        }
        throw new RuntimeException("There is no registered rigid body type for class " + rigidBody.getClass().getCanonicalName());
    }

    private void removeRigidBodyPanel() {
        if (editRigidBodyPanel != null) {
            editRigidBodyPanel.emptyDummy();
            editRigidBodyPanel.onClose(object.getMeshSceneNode(), manager3D);
            editRigidBodyPanel.getParent().remove(editRigidBodyPanel);
            editRigidBodyPanel = null;
        }
    }

    private void setBulletRigidBodyEditPanel(IBulletRigidBodyEditPanel panel) {
        add(panel, RowLayout.NewRow);
        editRigidBodyPanel = panel;
    }

    private DefaultFloatTextField createTextField() {
        return onChangeFloatProperty(new DefaultFloatTextField());
    }

    private DefaultFloatTextField onChangeFloatProperty(final DefaultFloatTextField textField) {
        textField.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            public void valueChanged(Float value) {
                if (textField == massEB) {
                    data.setMass(value);
                } else if (textField == restitutionEB) {
                    data.setRestitution(value);
                } else if (textField == frictionEB) {
                    data.setFriction(value);
                } else if (textField == rollingFrictionEB) {
                    data.setRollingFriction(value);
                } else if (textField == ccdMotionThresholdEB) {
                    data.setCcdMotionThreshold(value);
                } else if (textField == contactProcessingThresholdEB) {
                    data.setContactProcessingThreshold(value);
                } else if (textField == ccdSweptRadiusEB) {
                    data.setCcdSweptRadius(value);
                } else if (textField == linearDampingEB) {
                    data.setLinearDamping(value);
                } else if (textField == angularDampingEB) {
                    data.setAngularDamping(value);
                } else if (textField == deactivationTimeEB) {
                    data.setDeactivationTime(value);
                } else if (textField == hitFractionEB) {
                    data.setHitFraction(value);
                } else {
                    String message = "The text field " + textField.getName() + " is unprocessed";
                    utils.logError(message);
                    throw new RuntimeException(message);
                }
            }
        });

        return textField;
    }

    private Vector3dEditor onChangeVector(final Vector3dEditor vectorEditor) {
        vectorEditor.setValueChangedEvent(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                vector3df vector = vectorEditor.getValue();
                if (vectorEditor == linearVelocityEB) {
                    data.setLinearVelocity(vector);
                } else if (vectorEditor == linearFactorEB) {
                    data.setLinearFactor(vector);
                } else if (vectorEditor == angularVelocityEB) {
                    data.setAngularVelocity(vector);
                } else if (vectorEditor == angularFactorEB) {
                    data.setAngularFactor(vector);
                } else if (vectorEditor == anisotropicFrictionEB) {
                    data.setAnisotropicFriction(vector);
                } else if (vectorEditor == offsetPositionEB) {
                    data.setOffsetPosition(vector);
                } else if (vectorEditor == offsetRotationEB) {
                    data.setOffsetRotation(vector);
                } else {
                    String message = "The vector field is unprocessed";
                    utils.logError(message);
                    throw new RuntimeException(message);
                }
            }
        });

        return vectorEditor;
    }

    private hCheckBox onChangeBoolean(final hCheckBox checkBox) {
        checkBox.setValueChangedEvent(new ValueChangedEvent<Boolean>() {
            @Override
            public void valueChanged(Boolean value) {
                if (checkBox == disableDeactivationCB) {
                    data.setDisableDeactivation(value);
                } else if (checkBox == kinematicCB) {
                    data.setKinematic(value);
                } else if (checkBox == characterCB) {
                    data.setCharacter(value);
                } else if (checkBox == staticCB) {
                    data.setStaticBody(value);
                } else {
                    String message = "The boolean field is unprocessed";
                    utils.logError(message);
                    throw new RuntimeException(message);
                }
            }
        });
        return checkBox;
    }

    public BulletPhysicsPanel loadFromModel(Basic3dObject object, PhysicsData data) {
        this.object = object;
        this.data = data;
        fillFromModel(data);
        return this;
    }

    private void fillFromModel(PhysicsData data) {
        massEB.setValue(data.getMass());
        restitutionEB.setValue(data.getRestitution());
        frictionEB.setValue(data.getFriction());
        rollingFrictionEB.setValue(data.getRollingFriction());
        linearFactorEB.setValue(data.getLinearFactor());
        angularFactorEB.setValue(data.getAngularFactor());
        ccdMotionThresholdEB.setValue(data.getCcdMotionThreshold());
        contactProcessingThresholdEB.setValue(data.getContactProcessingThreshold());
        ccdSweptRadiusEB.setValue(data.getCcdSweptRadius());
        linearDampingEB.setValue(data.getLinearDamping());
        angularDampingEB.setValue(data.getAngularDamping());
        deactivationTimeEB.setValue(data.getDeactivationTime());
        hitFractionEB.setValue(data.getHitFraction());
        linearVelocityEB.setValue(data.getLinearVelocity());
        angularVelocityEB.setValue(data.getAngularVelocity());
        anisotropicFrictionEB.setValue(data.getAnisotropicFriction());
        offsetPositionEB.setValue(data.getOffsetPosition());
        offsetRotationEB.setValue(data.getOffsetRotation());
        disableDeactivationCB.setValue(data.isDisableDeactivation());
        kinematicCB.setValue(data.isKinematic());
        characterCB.setValue(data.isCharacter());
        staticCB.setValue(data.isStaticBody());

        removeRigidBodyPanel();
        setRigidBodyType(data.getRigidBody());
        rigidBodyTypeCB.setSelectedItem(getBulletRigidBodyType(data.getRigidBody()));
    }

    @Override
    public RenderEvent registerAfterRenderBeforeGuiEvent() {
        return new RenderEvent() {
            @Override
            public void onRender() {
                if (editRigidBodyPanel != null) {
                    editRigidBodyPanel.onDrawBeforeGui(object.getMeshSceneNode(), manager3D);
                }
            }
        };
    }

    @Override
    public boolean isDeleteable() {
        return true;
    }

    @Override
    public void onDeleteComponent() {
        object.removeHComponent(data);
    }
}
